import datetime
from logging import Logger
from typing import Any

from task_service.task_context import EquInfo, StorerDeliverLayer
from x_pc.core import Device
from x_pc.core.carry.carry_101_ddj import DDJ
from x_pc.core.resource_config import ResourceType
from x_pc.core.unit.unit_201_zbsx import ZBSX
from x_pc.db.common.model import WorkType
from x_pc.model.model import Loc


class SysDataCtx(object):
    """
    系统数据上下文
    设备初始化 所需的全部数据
    """
    ID_DB_KEY = 'id'
    # 设备类型 字段
    DEVICE_TYPE_DB_KEY = 'equ_type'
    # 设备编码 字段
    DEVICE_CODE_DB_KEY = 'equ_code'
    # 工作类型 字段
    DEVICE_WORK_TYPE_DB_KEY = 'work_type'
    # 设备是否启用  '1启用、2不启用'
    DEVICE_ENABLE_DB_KEY = 'enable'
    # 巷道 字段
    ROADWAY_DB_KEY = 'roadway'
    # 左右方向 字段
    LEFT_RIGHT_DB_KEY = 'letf_right'
    # 层 字段
    LAYER_DB_KEY = 'layer'
    # 出入标识 字段
    INOUT_MODE_DB_KEY = 'inout_mode'

    # 栈板巷道 名称前缀
    ZB_LANE_PRE_KEY = '1'
    # 件箱巷道 名称前缀
    JX_LANE_PRE_KEY = '2'

    # 栈板巷道数据
    zb_lane_data = {}
    # 件箱巷道数据
    jx_lane_data = {}

    # 搬运设备 数据
    _carry_data = []
    carry_data_dict = {}
    carry_id_data_dict = {}

    # 传送带设备 数据
    _transport_data = []
    transport_data_dict = {}
    transport_id_data_dict = {}

    # 工作单元设备 数据
    _unit_data = []
    unit_data_dict = {}
    unit_id_data_dict = {}
    unit_code_data_dict = {}

    # 工作中心 数据
    _center_data = []
    center_data_dict = {}
    center_id_data_dict = {}
    center_loc_data = []
    center_capacity_data = []

    # 设备可到区域配置 (件箱、栈板 巷道)
    equ_control_area_config = []

    # work_type 工作类型
    _work_type_list = []
    work_type_id_dict = {}

    # 巷道位置距离 (算法启动时，提前写入)
    lane_loc_distance_dict = {}

    # 商家标识字段
    _store_layer_dict = {}

    # 设备 初始化数据
    init_equ_dict = {}
    # 设备工作类型限制
    work_station_dict = {}

    # Sim模拟时长限制 (秒)
    sim_time_limit = 0

    # 作业可在提前发车前 排入的提前量 (秒)
    advance_vehicle_time = 0

    # 是否指定出货口
    is_designate_outlet: bool = False

    # 设置任务出货口延续的最长任务开始时间
    port_continued_time = 8

    # 是否指定出货口参数
    is_designate_outlet = False

    # CommandResourceService 路径获取
    crs = None

    # 基准时间
    base_time: float = 0

    # [205EMS交付口 / 206负一楼自制] 交付口 数据分析
    factory_port_dict: dict[int, dict[str, dict[str, Any]]] = {}
    # [205EMS交付口 / 206负一楼自制] 实时分配 回写数据
    factory_port: dict[int, dict[str, list]] = {}

    # [205EMS交付口 / 206负一楼自制] 上一波次 交付口分配数据
    _prev_factory_port: dict[int, dict[str, list]] = {}

    # [205EMS交付口 / 206负一楼自制] 当前 所有交付口的 执行任务数量
    _statistical_factory_port: dict[int, dict[str, int]] = {}

    # 急料订单 添加时间
    urgent_order_add_date_dict: dict[int, str] = {}

    # 在库 上一波次 交付口分配数据
    _zk_prev_factory_port: dict[int, dict[str, list]] = {}
    # 在库任务 出货口分配 [210, ['3380', ['order_123123', '***']]]
    zk_factory_port_dict: dict[int, dict[str, dict[str, Any]]] = {}
    zk_factory_port: dict[str, list] = {}

    # (发货) 出货口
    ch_ports: set[int] = set()
    # (在库) 出货口
    zk_ports: set[int] = set()
    # (在库) 需要进行出货口隔离的
    inv_isolated_task: list[str] = list()

    # Logger 内部使用
    logger: Logger = None

    # 设备缺失 日志标识 (避免重复打印日志)
    uk_notfound_log_flag: dict[str, bool] = {}

    # 设备耗时系数，系统初始化时设置
    _time_cost_ratio: float = 1

    # 设备不可用时间段
    pending_devices: dict[str, list[tuple[float, float]]] = {}

    # 出-1自制/EMS 无需换自制箱的任务
    nosplit_skus: set[str] = set()
    # 需要自制换包的 SKU (前提: 交付对象是1/-1楼自制)
    split_skus: set[str] = set()

    # 工厂日历
    _work_calendar: list[tuple[float, float]] = []

    # 关联仓库
    ware_house = None

    # 原始任务数据
    raw_task_dict = {}

    def __init__(self):
        self.zb_lane_data = {}
        self.jx_lane_data = {}

        self._carry_data = []
        self.carry_data_dict = {}
        self.carry_id_data_dict = {}

        self._transport_data = []
        self.transport_data_dict = {}
        self.transport_id_data_dict = {}

        self._unit_data = []
        self.unit_data_dict = {}
        self.unit_id_data_dict = {}
        self.unit_code_data_dict = {}

        self._center_data = []
        self.center_data_dict = {}
        self.center_id_data_dict = {}
        self.center_loc_data = []
        self.center_capacity_data = []

        self.equ_control_area_config = []

        self._work_type_list = []
        self.work_type_id_dict = {}

        self.lane_loc_distance_dict = {}

        self._store_layer_dict = {}

        self.init_equ_dict = {}
        self.work_station_dict = {}

        self.factory_port_dict: dict[int, dict[str, dict[str, Any]]] = {}
        self.factory_port: dict[int, dict[str, list]] = {}

        self._prev_factory_port: dict[int, dict[str, list]] = {}

        self.urgent_order_add_date_dict: dict[int, str] = {}

        self._zk_prev_factory_port: dict[int, dict[str, list]] = {}
        self.zk_factory_port_dict: dict[int, dict[str, dict[str, Any]]] = {}
        self.zk_factory_port: dict[int, list] = {}

        self.ch_ports: set[int] = set()
        self.zk_ports: set[int] = set()
        self.inv_isolated_task: list[str] = list()

        self.uk_notfound_log_flag: dict[str, bool] = {}

        self.pending_devices: dict[str, list[tuple[float, float]]] = {}

        self.nosplit_skus: set[str] = set()
        self.split_skus: set[str] = set()

        self.raw_task_dict = {}

    @property
    def store_layer_dict(self) -> dict[str, StorerDeliverLayer]:
        return self._store_layer_dict

    @store_layer_dict.setter
    def store_layer_dict(self, data):
        self.logger.info(f'[PC] INIT 交付对象楼层: {data}')
        self._store_layer_dict = data

    @property
    def carry_data(self):
        return self._carry_data

    @carry_data.setter
    def carry_data(self, data):
        self._carry_data = data
        self.__transform_data(data, self.carry_data_dict)
        self.__transform_id_data(ResourceType.CARRY, data, self.carry_id_data_dict)

    @property
    def transport_data(self):
        return self._transport_data

    @transport_data.setter
    def transport_data(self, data):
        self._transport_data = data
        self.__transform_data(data, self.transport_data_dict)
        self.__transform_id_data(ResourceType.TRANSPORT, data, self.transport_id_data_dict)

    @property
    def unit_data(self):
        return self._unit_data

    @unit_data.setter
    def unit_data(self, data):
        self._unit_data = data
        self.__transform_data(data, self.unit_data_dict)
        self.__transform_id_data(ResourceType.UNIT, data, self.unit_id_data_dict)
        self.__transform_code_data(ResourceType.UNIT, data, self.unit_code_data_dict)

    @property
    def center_data(self):
        return self._center_data

    @center_data.setter
    def center_data(self, data):
        self._center_data = data
        self.__transform_data(data, self.center_data_dict)
        self.__transform_id_data(ResourceType.CENTER, data, self.center_id_data_dict)

    @property
    def work_type_list(self):
        return self._work_type_list

    @work_type_list.setter
    def work_type_list(self, data):
        self._work_type_list = data
        work_type_dict = {}
        for wt in self._work_type_list:
            wt: WorkType
            work_type_dict[wt.work_type] = wt
        self.work_type_id_dict = work_type_dict

    @property
    def time_cost_ratio(self) -> float:
        return self._time_cost_ratio

    @time_cost_ratio.setter
    def time_cost_ratio(self, data):
        """
        设置 设备运行时长系数
        """
        self.logger.info(f'[PC] INIT 设备运行时长系数: {data}')
        self._time_cost_ratio = data

    @property
    def prev_factory_port(self):
        return self._prev_factory_port

    @prev_factory_port.setter
    def prev_factory_port(self, data):
        self.logger.info(f'[PC] INIT 历史出货口分配: {data}')
        self._prev_factory_port = data

    @property
    def zk_prev_factory_port(self):
        return self._zk_prev_factory_port

    @zk_prev_factory_port.setter
    def zk_prev_factory_port(self, data):
        self.logger.info(f'[PC] INIT (在库) 历史出货口分配: {data}')
        self._zk_prev_factory_port = data

    @property
    def statistical_factory_port(self):
        return self._statistical_factory_port

    @statistical_factory_port.setter
    def statistical_factory_port(self, data):
        self.logger.info(f'[PC] INIT 历史出货口出货量: {data}')
        self._statistical_factory_port = data

    @property
    def work_calendar(self):
        return self._work_calendar

    @work_calendar.setter
    def work_calendar(self, data):
        work_time = [(self.format_ts(_[0]), self.format_ts(_[1])) for _ in data]
        work_time.sort(key=lambda _: _[0])
        self.logger.info(f'[PC] INIT 工厂日历: {work_time}')
        self._work_calendar = data

    def __transform_data(self, data: list, data_dict: dict):
        for d in data:
            key = self.gen_key(d[self.DEVICE_TYPE_DB_KEY], d[self.DEVICE_CODE_DB_KEY])
            data_dict[key] = d

    def __transform_id_data(self, resource_type: ResourceType, data: list, data_dict: dict):
        for d in data:
            key = self.gen_key(resource_type.value, d[self.ID_DB_KEY])
            data_dict[key] = d

    def __transform_code_data(self, resource_type: ResourceType, data: list, data_dict: dict):
        for d in data:
            key = d[self.DEVICE_CODE_DB_KEY]
            data_dict[key] = d

    def get_data(self, device: Device) -> list:
        """
        获取某类型的设备数据
        :param device:
        :return:
        """
        attr = f'{device.category}_data'
        data = getattr(self, attr)
        return [_ for _ in data if self.DEVICE_TYPE_DB_KEY in _ and _[self.DEVICE_TYPE_DB_KEY] == device.device_type()]

    def get_data_category(self, category: str, device_type: str | int, device_code: str | int):
        key = self.gen_key(device_type, device_code)
        data_map: dict = getattr(self, f'{category}_data_dict')
        return data_map.get(key)

    def get_data_by_key(self, device_type: str, device_code: str):
        """
        根据 设备类型、设备Code 获取设备信息

        :param device_type: 设备类型
        :param device_code: 设备Code
        :return:
        """
        key = self.gen_key(device_type, device_code)
        if key in self.carry_data_dict:
            return self.carry_data_dict.get(key)
        if key in self.transport_data:
            return self.transport_data_dict.get(key)
        if key in self.unit_data_dict:
            return self.unit_data_dict.get(key)
        return None

    def get_data_by_id(self, resource_type: ResourceType, id: str):
        if not resource_type or not id:
            return None
        key = self.gen_key(resource_type.value, id)
        if resource_type == ResourceType.CARRY:
            return self.carry_id_data_dict.get(key)
        if resource_type == ResourceType.TRANSPORT:
            return self.transport_id_data_dict.get(key)
        if resource_type == ResourceType.UNIT:
            return self.unit_id_data_dict.get(key)
        if resource_type == ResourceType.CENTER:
            return self.center_id_data_dict.get(key)

    def get_hctsj(self):
        """
        换层提升机 数据获取
        :return:
        """
        from x_pc.core.carry.carry_104_hctsj import HCTSJ
        hctsj_list = [_ for _ in self._carry_data if _[self.DEVICE_TYPE_DB_KEY] == HCTSJ.device_type()]
        return hctsj_list[0] if hctsj_list else None

    def get_lane_loc(self, loc: Loc | str):
        """
        根据货物位置 获取耗时
        :param loc:
        :return:
        """
        from x_pc.db.sf_data import get_loc_distance
        loc_str = loc.raw_loc if isinstance(loc, Loc) else loc
        if loc_str not in self.lane_loc_distance_dict:
            self.lane_loc_distance_dict[loc_str] = get_loc_distance(loc_str)
        return self.lane_loc_distance_dict[loc_str]

    def get_zb_lane_data(self) -> dict:
        """
        获取 栈板巷道 数据
        :return:
        """
        if self.zb_lane_data:
            return self.zb_lane_data

        lane_data = {}
        zb_data = [_ for _ in self.equ_control_area_config if
                   self.ROADWAY_DB_KEY in _ and _[self.ROADWAY_DB_KEY].startswith(self.ZB_LANE_PRE_KEY)]
        zb_data_dict = {}
        for zb in zb_data:
            key = zb[self.ROADWAY_DB_KEY]
            if key not in zb_data_dict:
                zb_data_dict[key] = {}
            zb_data_dict[key][zb[self.DEVICE_TYPE_DB_KEY]] = zb
        # TODO: 清理一下数据，实际可能需要修改
        for key in tuple(zb_data_dict.keys()):
            if len(zb_data_dict[key]) != 2:
                zb_data_dict.pop(key)
        # 继续处理数据 查找具体的设备
        for key in zb_data_dict:
            ddj_code = zb_data_dict[key][str(DDJ.device_type())][self.DEVICE_CODE_DB_KEY]
            ddj = self.get_data_category(DDJ.category, DDJ.device_type(), ddj_code)
            zbsx_code = zb_data_dict[key][str(ZBSX.device_type())][self.DEVICE_CODE_DB_KEY]
            zbsx = self.get_data_category(ZBSX.category, ZBSX.device_type(), zbsx_code)
            # 赋值
            lane_data[key] = {str(DDJ.device_type()): ddj, str(ZBSX.device_type()): zbsx}

        self.zb_lane_data = lane_data
        return self.zb_lane_data

    def get_jx_lane_data(self):
        """
        获取 件箱巷道 数据
        :return:
        """
        if self.jx_lane_data:
            return self.jx_lane_data

        jx_data = [_ for _ in self.equ_control_area_config if
                   self.ROADWAY_DB_KEY in _ and _[self.ROADWAY_DB_KEY].startswith(self.JX_LANE_PRE_KEY)]
        jx_data_dict = {}
        for jx in jx_data:
            key = jx[self.ROADWAY_DB_KEY]
            if key not in jx_data_dict:
                jx_data_dict[key] = {}
            subkey = jx[self.DEVICE_TYPE_DB_KEY]
            if subkey not in jx_data_dict[key]:
                jx_data_dict[key][subkey] = []
            # 校验下是否可用
            data = self.get_data_by_key(jx[self.DEVICE_TYPE_DB_KEY], jx[self.DEVICE_CODE_DB_KEY])
            if not data or self.is_enable(data):
                jx_data_dict[key][subkey].append((jx, data))
        #
        self.jx_lane_data = jx_data_dict
        return self.jx_lane_data

    def get_work_type_by_id(self, id: int) -> WorkType | None:
        """
        获取工作类型
        :param id:
        :return:
        """
        if id in self.work_type_id_dict:
            return self.work_type_id_dict[id]
        return None

    def is_valid_equ(self, device: Device) -> bool:
        """
        根据初始数据，判断是否设备是否有效
        - 1是可用，不存在的视为不可用
        - wops_transport_equ表的设备都视为可用
        - allEquInfo 里面没有wops_transport_equ的数据
        :param device:
        :return:
        """
        equ_type = device.device_type()
        equ_code = device.device_code()
        #
        return self.is_valid_device_raw(device.category, equ_type, equ_code)

    def is_valid_device_raw(self, resource_type: str, equ_type: int, equ_code: str) -> bool:
        """
        堆垛机	            ddj	    101	是
        穿梭车	            csc	    102	是
        提升机	            tsj	    103	是
        换层提升机	        hctsj	104	是
        栈板上下架接驳口	    zbsx	201	是
        件箱上下架接驳口	    jxsx	203	是
        件箱层接驳口	        jxcj	204	是
        GTP超市补货口	    jxb	    207	是
        栈板出库集货	        zbjh	209	是
        出入货口设备组	    iqccr	210	是
        周转箱出库集货	    zzxjh	216	是
        箱式出库集货	        xsjh	217	是
        跟随超市补货口	    gsb	    219	是
        零星发货超市出货口	gtpb	220	是
        箱式异常出货口	    xsycc	221	是
        自动称重盘点区	    zcz	    307	是
        环穿	            hc	    401	是
        传送	            cs	    402	是
        AGV搬运	            agvby	403	是
        """
        # 可用设备列表
        default_valid_device_code_list = (
            101,
            102,
            103,
            104,
            201,
            203,
            204,
            207,
            209,
            210,
            216,
            217,
            219,
            220,
            221,
            307,
            401,
            402,
            403,
        )
        if equ_type in default_valid_device_code_list:
            return True

        uk = EquInfo.gen_uk(equ_type, equ_code)
        if uk not in self.init_equ_dict:
            if uk not in self.uk_notfound_log_flag:
                self.logger.warning(
                    f'[PC] 设备无效 {resource_type} 设备类型type: {equ_type}, 设备编码code: {equ_code} 不在入参(allEquInfo)范围内')
            self.uk_notfound_log_flag[uk] = True
            return False
        equ = self.init_equ_dict.get(uk)
        if not equ.is_valid:
            if uk not in self.uk_notfound_log_flag:
                self.logger.warning(
                    f'[PC] 设备无效 {resource_type} 设备类型type: {equ_type}, 设备编码code: {equ_code} 在入参(allEquInfo)范围内，但不可用')
            self.uk_notfound_log_flag[uk] = True
            return False
        return True

    def get_pending_device(self, equ_type: int, equ_code: str, loc: Any = None) -> list:
        """
        获取设备的不可用时间
        """
        key = f'{equ_type}_{equ_code}'
        if key not in self.pending_devices:
            return []
        return self.pending_devices[key]

    @staticmethod
    def gen_key(device_type: str | int, device_code: str | int) -> str:
        return f'{device_type}_{device_code}'

    @staticmethod
    def is_enable(data: dict):
        return SysDataCtx.DEVICE_ENABLE_DB_KEY not in data or (data[SysDataCtx.DEVICE_ENABLE_DB_KEY] == 1)

    @staticmethod
    def format_ts(ts: float) -> str:
        return datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
